//package org.bouncycastle.asn1.eac;
package de.tsenger.certain.asn1.eac;

import java.math.BigInteger;
import java.util.Enumeration;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;

/**
 * an Iso7816ECDSAPublicKeyStructure structure.
 * <p/>
 * 
 * <pre>
 *  Certificate Holder Authorization ::= SEQUENCE {
 *  	ASN1TaggedObject primeModulusP;		// OPTIONAL
 *  	ASN1TaggedObject firstCoefA;			// OPTIONAL
 *  	ASN1TaggedObject secondCoefB;		// OPTIONAL
 *  	ASN1TaggedObject basePointG;			// OPTIONAL
 *  	ASN1TaggedObject orderOfBasePointR;	// OPTIONAL
 *  	ASN1TaggedObject publicPointY;		//REQUIRED
 *  	ASN1TaggedObject	cofactorF;			// OPTIONAL
 *  }
 * </pre>
 */
public class ECDSAPublicKey extends PublicKeyDataObject {
	private final ASN1ObjectIdentifier usage;
	private BigInteger primeModulusP; // OPTIONAL
	private BigInteger firstCoefA; // OPTIONAL
	private BigInteger secondCoefB; // OPTIONAL
	private byte[] basePointG; // OPTIONAL
	private BigInteger orderOfBasePointR; // OPTIONAL
	private byte[] publicPointY; // REQUIRED
	private BigInteger cofactorF; // OPTIONAL
	private int options;
	private static final int P = 0x01;
	private static final int A = 0x02;
	private static final int B = 0x04;
	private static final int G = 0x08;
	private static final int R = 0x10;
	private static final int Y = 0x20;
	private static final int F = 0x40;

	ECDSAPublicKey(ASN1Sequence seq) throws IllegalArgumentException {
		Enumeration<?> en = seq.getObjects();

		this.usage = ASN1ObjectIdentifier.getInstance(en.nextElement());

		options = 0;
		while (en.hasMoreElements()) {
			Object obj = en.nextElement();

			if (obj instanceof ASN1TaggedObject) {
				ASN1TaggedObject to = (ASN1TaggedObject) obj;
				switch (to.getTagNo()) {
				case 0x1:
					setPrimeModulusP(UnsignedInteger.getInstance(to).getValue());
					break;
				case 0x2:
					setFirstCoefA(UnsignedInteger.getInstance(to).getValue());
					break;
				case 0x3:
					setSecondCoefB(UnsignedInteger.getInstance(to).getValue());
					break;
				case 0x4:
					setBasePointG(ASN1OctetString.getInstance(to, false));
					break;
				case 0x5:
					setOrderOfBasePointR(UnsignedInteger.getInstance(to).getValue());
					break;
				case 0x6:
					setPublicPointY(ASN1OctetString.getInstance(to, false));
					break;
				case 0x7:
					setCofactorF(UnsignedInteger.getInstance(to).getValue());
					break;
				default:
					options = 0;
					throw new IllegalArgumentException("Unknown Object Identifier!");
				}
			} else {
				throw new IllegalArgumentException("Unknown Object Identifier!");
			}
		}
		if (options != 0x20 && options != 0x7F) {
			throw new IllegalArgumentException("All options must be either present or absent!");
		}
	}

	public ECDSAPublicKey(ASN1ObjectIdentifier usage, byte[] ppY) throws IllegalArgumentException {
		this.usage = usage;
		setPublicPointY(new DEROctetString(ppY));
	}

	public ECDSAPublicKey(ASN1ObjectIdentifier usage, BigInteger p, BigInteger a, BigInteger b, byte[] basePoint, BigInteger order, byte[] publicPoint,
			int cofactor) {
		this.usage = usage;
		setPrimeModulusP(p);
		setFirstCoefA(a);
		setSecondCoefB(b);
		setBasePointG(new DEROctetString(basePoint));
		setOrderOfBasePointR(order);
		setPublicPointY(new DEROctetString(publicPoint));
		setCofactorF(BigInteger.valueOf(cofactor));
	}

	@Override
	public ASN1ObjectIdentifier getUsage() {
		return usage;
	}

	public byte[] getBasePointG() {
		if ((options & G) != 0) {
			return basePointG;
		} else {
			return null;
		}
	}

	private void setBasePointG(ASN1OctetString basePointG) throws IllegalArgumentException {
		if ((options & G) == 0) {
			options |= G;
			this.basePointG = basePointG.getOctets();
		} else {
			throw new IllegalArgumentException("Base Point G already set");
		}
	}

	public BigInteger getCofactorF() {
		if ((options & F) != 0) {
			return cofactorF;
		} else {
			return null;
		}
	}

	private void setCofactorF(BigInteger cofactorF) throws IllegalArgumentException {
		if ((options & F) == 0) {
			options |= F;
			this.cofactorF = cofactorF;
		} else {
			throw new IllegalArgumentException("Cofactor F already set");
		}
	}

	public BigInteger getFirstCoefA() {
		if ((options & A) != 0) {
			return firstCoefA;
		} else {
			return null;
		}
	}

	private void setFirstCoefA(BigInteger firstCoefA) throws IllegalArgumentException {
		if ((options & A) == 0) {
			options |= A;
			this.firstCoefA = firstCoefA;
		} else {
			throw new IllegalArgumentException("First Coef A already set");
		}
	}

	public BigInteger getOrderOfBasePointR() {
		if ((options & R) != 0) {
			return orderOfBasePointR;
		} else {
			return null;
		}
	}

	private void setOrderOfBasePointR(BigInteger orderOfBasePointR) throws IllegalArgumentException {
		if ((options & R) == 0) {
			options |= R;
			this.orderOfBasePointR = orderOfBasePointR;
		} else {
			throw new IllegalArgumentException("Order of base point R already set");
		}
	}

	public BigInteger getPrimeModulusP() {
		if ((options & P) != 0) {
			return primeModulusP;
		} else {
			return null;
		}
	}

	private void setPrimeModulusP(BigInteger primeModulusP) {
		if ((options & P) == 0) {
			options |= P;
			this.primeModulusP = primeModulusP;
		} else {
			throw new IllegalArgumentException("Prime Modulus P already set");
		}
	}

	public byte[] getPublicPointY() {
		if ((options & Y) != 0) {
			return publicPointY;
		} else {
			return null;
		}
	}

	private void setPublicPointY(ASN1OctetString publicPointY) throws IllegalArgumentException {
		if ((options & Y) == 0) {
			options |= Y;
			this.publicPointY = publicPointY.getOctets();
		} else {
			throw new IllegalArgumentException("Public Point Y already set");
		}
	}

	public BigInteger getSecondCoefB() {
		if ((options & B) != 0) {
			return secondCoefB;
		} else {
			return null;
		}
	}

	private void setSecondCoefB(BigInteger secondCoefB) throws IllegalArgumentException {
		if ((options & B) == 0) {
			options |= B;
			this.secondCoefB = secondCoefB;
		} else {
			throw new IllegalArgumentException("Second Coef B already set");
		}
	}

	public boolean hasDomainParameters() {
		return ((primeModulusP!=null)&&(firstCoefA!=null)&&(secondCoefB!=null)&&(basePointG!=null)&&(orderOfBasePointR!=null)&&(cofactorF!=null));

	}

	public ASN1EncodableVector getASN1EncodableVector(ASN1ObjectIdentifier oid, boolean publicPointOnly) {
		ASN1EncodableVector v = new ASN1EncodableVector();
		v.add(oid);

		if (!publicPointOnly) {
			v.add(new UnsignedInteger(0x01, getPrimeModulusP()));
			v.add(new UnsignedInteger(0x02, getFirstCoefA()));
			v.add(new UnsignedInteger(0x03, getSecondCoefB()));
			v.add(new DERTaggedObject(false, 0x04, new DEROctetString(getBasePointG())));
			v.add(new UnsignedInteger(0x05, getOrderOfBasePointR()));
		}
		v.add(new DERTaggedObject(false, 0x06, new DEROctetString(getPublicPointY())));
		if (!publicPointOnly) {
			v.add(new UnsignedInteger(0x07, getCofactorF()));
		}

		return v;
	}

	@Override
	public ASN1Primitive toASN1Primitive() {
		if (getPrimeModulusP() != null) //Only CVCA certificates contain full public key with Domain Parameters
			return new DERSequence(getASN1EncodableVector(usage, false));
		else // DV and Terminal certificates MUST NOT contain domain parameters
			return new DERSequence(getASN1EncodableVector(usage, true));
	}
}